﻿//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;

namespace AppToolkit.WF.Utils
{

    /// <summary>
    /// Implementation of the <see cref="IAsyncResult"/> interface.
    /// </summary>
    public abstract class AsyncResult : IAsyncResult
    {
        static AsyncCallback _asyncCompletionWrapperCallback;
        readonly AsyncCallback _callback;
        bool _completedSynchronously;
        bool _endCalled;
        Exception _exception;
        bool _isCompleted;
        ManualResetEvent _manualResetEvent;
        AsyncCompletion _nextAsyncCompletion;
        readonly object _state;
        readonly object _lock;

        /// <summary>
        /// CTOR to create an AsyncResult.
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="state"></param>
        protected AsyncResult(AsyncCallback callback, object state)
        {
            this._callback = callback;
            this._state = state;
            this._lock = new object();
        }

        /// <summary>
        /// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
        /// </summary>
        /// <returns>
        /// A user-defined object that qualifies or contains information about an asynchronous operation.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public object AsyncState
        {
            get
            {
                return this._state;
            }
        }

        /// <summary>
        /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public WaitHandle AsyncWaitHandle
        {
            get
            {
                if (this._manualResetEvent != null)
                {
                    return this._manualResetEvent;
                }

                lock (this.Lock)
                {
                    if (this._manualResetEvent == null)
                    {
                        this._manualResetEvent = new ManualResetEvent(this._isCompleted);
                    }
                }

                return this._manualResetEvent;
            }
        }

        /// <summary>
        /// Gets a value that indicates whether the asynchronous operation completed synchronously.
        /// </summary>
        /// <returns>
        /// true if the asynchronous operation completed synchronously; otherwise, false.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public bool CompletedSynchronously
        {
            get
            {
                return this._completedSynchronously;
            }
        }

        /// <summary>
        /// Returns true, if a callback is registered.
        /// </summary>
        public bool HasCallback
        {
            get
            {
                return this._callback != null;
            }
        }

        /// <summary>
        /// Returns true, if the async operation has completed.
        /// </summary>
        public bool IsCompleted
        {
            get
            {
                return this._isCompleted;
            }
        }

        object Lock
        {
            get
            {
                return this._lock;
            }
        }

        /// <summary>
        /// Called when the wrapped operation completed.
        /// </summary>
        /// <param name="completedSynchronously"></param>
        protected void Complete(bool completedSynchronously)
        {
            if (this._isCompleted)
            {
                // It's a bug to call Complete twice.
                throw new InvalidProgramException();
            }

            this._completedSynchronously = completedSynchronously;

            if (completedSynchronously)
            {
                // If we completedSynchronously, then there's no chance that the manualResetEvent was created so
                // we don't need to worry about a race
                this._isCompleted = true;
            }
            else
            {
                lock (this.Lock)
                {
                    this._isCompleted = true;
                    if (this._manualResetEvent != null)
                    {
                        this._manualResetEvent.Set();
                    }
                }
            }

            if (this._callback != null)
            {
                try
                {
                    this._callback(this);
                }
                catch (Exception e)
                {
                    throw new InvalidProgramException("Async callback threw an Exception", e);
                }
            }
        }

        /// <summary>
        /// Call when operation results in an error.
        /// </summary>
        /// <param name="completedSynchronously"></param>
        /// <param name="exception"></param>
        protected void Complete(bool completedSynchronously, Exception exception)
        {
            this._exception = exception;
            Complete(completedSynchronously);
        }

        static void AsyncCompletionWrapperCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                return;
            }

            var thisPtr = (AsyncResult)result.AsyncState;
            var callback = thisPtr.GetNextCompletion();

            bool completeSelf = false;
            Exception completionException = null;
            try
            {
                completeSelf = callback(result);
            }
            catch (Exception e)
            {
                if (IsFatal(e))
                {
                    throw;
                }
                completeSelf = true;
                completionException = e;
            }

            if (completeSelf)
            {
                thisPtr.Complete(false, completionException);
            }
        }

        /// <summary>
        /// Returns true, if the passed exception one of its inner exceptions is not recoverable.
        /// Non recoverable types are: <see cref="OutOfMemoryException"/>, <see cref="InsufficientMemoryException"/>, 
        /// <see cref="ThreadAbortException"/>, <see cref="AccessViolationException"/> and <see cref="SEHException"/>
        /// </summary>
        /// <param name="exception"></param>
        /// <returns></returns>
        public static bool IsFatal(Exception exception)
        {
            while (exception != null)
            {
                if ((exception is OutOfMemoryException && !(exception is InsufficientMemoryException)) ||
                    exception is ThreadAbortException ||
                    exception is AccessViolationException ||
                    exception is SEHException)
                {
                    return true;
                }

                // These exceptions aren't themselves fatal, but since the CLR uses them to wrap other exceptions,
                // we want to check to see whether they've been used to wrap a fatal exception.  If so, then they
                // count as fatal.
                if (exception is TypeInitializationException ||
                    exception is TargetInvocationException)
                {
                    exception = exception.InnerException;
                }
                else
                {
                    break;
                }
            }

            return false;
        }

        /// <summary>
        /// Unclear intention of method.
        /// </summary>
        /// <param name="callback"></param>
        /// <returns></returns>
        protected AsyncCallback PrepareAsyncCompletion(AsyncCompletion callback)
        {
            this._nextAsyncCompletion = callback;
            return AsyncResult._asyncCompletionWrapperCallback ??
                   (AsyncResult._asyncCompletionWrapperCallback = new AsyncCallback(AsyncCompletionWrapperCallback));
        }

        AsyncCompletion GetNextCompletion()
        {
            AsyncCompletion result = this._nextAsyncCompletion;
            this._nextAsyncCompletion = null;
            return result;
        }

        /// <summary>
        /// Call to wait for completion of the underlying operation.
        /// </summary>
        /// <typeparam name="TAsyncResult"></typeparam>
        /// <param name="result"></param>
        /// <returns></returns>
        protected static TAsyncResult End<TAsyncResult>(IAsyncResult result)
            where TAsyncResult : AsyncResult
        {
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            var asyncResult = result as TAsyncResult;

            if (asyncResult == null)
            {
                throw new ArgumentException("Invalid AsyncResult", "result");
            }

            if (asyncResult._endCalled)
            {
                throw new InvalidOperationException("AsyncResult already ended");
            }

            asyncResult._endCalled = true;

            if (!asyncResult._isCompleted)
            {
                asyncResult.AsyncWaitHandle.WaitOne();
            }

            if (asyncResult._manualResetEvent != null)
            {
                asyncResult._manualResetEvent.Close();
            }

            if (asyncResult._exception != null)
            {
                throw asyncResult._exception;
            }

            return asyncResult;
        }

        /// <summary>
        /// can be utilized by subclasses to write core completion code for both the sync and async paths
        /// in one location, signaling chainable synchronous completion with the boolean result,// 
        /// and leveraging PrepareAsyncCompletion for conversion to an AsyncCallback.// 
        /// NOTE: requires that "this" is passed in as the state object to the asynchronous sub-call being used with a completion routine.
        /// </summary>
        /// <param name="result"></param>
        /// <returns></returns>
        protected delegate bool AsyncCompletion(IAsyncResult result);
    }
}
